﻿#region using statements

using System;
using System.Collections.Generic;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Media;

using NGen.Managers.Logging;

using Nuclex.Support.Collections;

#endregion

namespace NGen.Managers.Audio
{

	#region using declarations

	#endregion

	/// <summary>
	/// 	Class to handle the playing of sound effects and songs
	/// </summary>
	public sealed class ManagerAudio2D : GameManager, IManagerAudio2D
	{
		#region local fields

		/// <summary>
		/// 	the song playlist
		/// </summary>
		private readonly Deque<Song> m_dqSongQueue = new Deque<Song>();

		/// <summary>
		/// 	the set of all sound effect instances which are waiting to be cleaned up
		/// </summary>
		private readonly HashSet<SoundEffectInstance> m_hsDoneSoundEffects = new HashSet<SoundEffectInstance>();

		/// <summary>
		/// 	the set of all playing sound effect instance
		/// </summary>
		private readonly HashSet<SoundEffectInstance> m_hsSoundEffects = new HashSet<SoundEffectInstance>();

		/// <summary>
		/// 	sound effect default volume
		/// </summary>
		private float m_soundEffectVolume = 1.0f;

		#endregion

		#region local methods

		/// <summary>
		/// 	This method has been registered with a listener; whenever the active song
		/// 	is changed the listener will call this method.
		/// </summary>
		/// <param name = "p_sender">The listener that has been registered with this method will be the sender</param>
		/// <param name = "p_e">This is the event argument data that is passed in</param>
		private void _MediaPlayerActiveSongChanged(object p_sender, EventArgs p_e) {
			if(m_dqSongQueue.Count > 0) {
				Logger.Write(Logger.BuildString(m_dqSongQueue.First.Name).ToString(), CurrentLoggingLevel);
			}
		}

		/// <summary>
		/// 	This method is registered with the MediaStateChanged event listener.  Whenever the media
		/// 	changes and it changes to being stopped; the next song in the song queue will be played.
		/// </summary>
		/// <param name = "p_sender">The listener that has been registered with this method will be the sender</param>
		/// <param name = "p_e">This is the event argument data that is passed in</param>
		private void _MediaPlayerMediaStateChanged(object p_sender, EventArgs p_e) {
			Logger.Write(Logger.BuildString("Music is now ").Append(MediaPlayer.State).ToString(), CurrentLoggingLevel);
		}

		#endregion

		#region constructors

		public ManagerAudio2D() {
			CurrentLoggingLevel = GameEngine.DefaultLoggingLevel;
			Logger = GameEngine.GetService<IManagerLogs>().Log<ManagerAudio2D>();
			SkipUpdate = false;
			SkipDraw = true;
			UpdateOrder = 0;
			//wire up the events
			MediaPlayer.ActiveSongChanged += _MediaPlayerActiveSongChanged;
			MediaPlayer.MediaStateChanged += _MediaPlayerMediaStateChanged;
		}

		#endregion

		#region IGameManager implementation

		/// <summary>
		/// 	Should this manager skip its update call
		/// </summary>
		public new bool SkipUpdate {
			get { return false; }
			set {
				if(value) {
					PauseAllSoundEffects();
					PauseSong();
				} else {
					ResumeSong();
					ResumeAllSoundEffects();
				}
			}
		}

		/// <summary>
		/// 	Should this manager skip its draw call
		/// </summary>
		public new bool SkipDraw {
			get { return true; }
			set { }
		}

		/// <summary>
		/// 	Cleans up sound effects that are done playing
		/// </summary>
		/// <param name = "p_gameTime">game time</param>
		internal override void Update(GameTime p_gameTime) {
			//update all sound effects
			foreach(var sfx in m_hsSoundEffects) {
				if(sfx.State ==
				   SoundState.Stopped) {
					m_hsDoneSoundEffects.Add(sfx);
				}
			}

			//cleanup all of the finished effects
			foreach(var sfx in m_hsDoneSoundEffects) {
				sfx.Dispose();
				m_hsSoundEffects.Remove(sfx);
			}
			m_hsDoneSoundEffects.Clear();
		}

		/// <summary>
		/// 	Cleanup the manager
		/// </summary>
		internal override void Cleanup() {
			StopAllSoundEffects();
			ClearPlaylist();

			foreach(var sfx in m_hsSoundEffects) {
				sfx.Stop();
				sfx.Dispose();
			}
			m_hsSoundEffects.Clear();

			foreach(var sfx in m_hsDoneSoundEffects) {
				sfx.Dispose();
			}
			m_hsDoneSoundEffects.Clear();

			MediaPlayer.ActiveSongChanged -= _MediaPlayerActiveSongChanged;
			MediaPlayer.MediaStateChanged -= _MediaPlayerMediaStateChanged;
		}

		#endregion

		#region IManagerAudio2D Members

		/// <summary>
		/// 	Get or set Song volume. 0.0 through 1.0
		/// </summary>
		public float SongVolume {
			get { return MediaPlayer.Volume; }
			set {
				if(value < 0.0f) {
					MediaPlayer.Volume = 0.0f;
				} else if(value >= 1.0f) {
					MediaPlayer.Volume = 1.0f;
				} else {
					MediaPlayer.Volume = value;
				}

				Logger.Write(Logger.BuildString(GetType().Name).Append(": Song volume changed").ToString(), CurrentLoggingLevel);
			}
		}

		/// <summary>
		/// 	Get or set Song volume. 0.0 through 1.0
		/// </summary>
		public float SoundEffectVolume {
			get { return m_soundEffectVolume; }
			set {
				if(value < 0.0f) {
					m_soundEffectVolume = 0.0f;
				} else if(value >= 1.0f) {
					m_soundEffectVolume = 1.0f;
				} else {
					m_soundEffectVolume = value;
				}
				Logger.Write(Logger.BuildString(GetType().Name).Append(": Sound effects volume changed").ToString(), CurrentLoggingLevel);
			}
		}

		/// <summary>
		/// 	Get or set whether or not the song queue is looping
		/// </summary>
		public bool IsLooping {
			get { return MediaPlayer.IsRepeating; }
			set { MediaPlayer.IsRepeating = value; }
		}

		#endregion

		#region Playlist methods

		/// <summary>
		/// 	This method adds the specified song to the play queue
		/// </summary>
		/// <param name = "p_song">The song to be added to the que</param>
		public void AddSongToQueue(Song p_song) {
			if(!m_dqSongQueue.Contains(p_song)) {
				m_dqSongQueue.AddLast(p_song);
			}
			Logger.Write(Logger.BuildString(GetType().Name).Append(": Song added to queue").ToString(), CurrentLoggingLevel);
		}

		/// <summary>
		/// 	This method will remove a song from the play queue.
		/// </summary>
		/// <param name = "p_song">The song to be removed</param>
		public void RemoveSongFromQueue(Song p_song) {
			if(m_dqSongQueue.Contains(p_song)) {
				m_dqSongQueue.Remove(p_song);
			}
			Logger.Write(Logger.BuildString(GetType().Name).Append(": Song removed from queue").ToString(), CurrentLoggingLevel);
		}

		/// <summary>
		/// 	This method will start the song queue if there are no songs currently playing
		/// </summary>
		public void PlaySongPlaylist() {
			if(m_dqSongQueue.Count > 0) {
				MediaPlayer.Play(m_dqSongQueue.First);
			}

			MediaPlayer.IsRepeating = false;
		}

		/// <summary>
		/// 	This method will play the next song in the play queue
		/// </summary>
		public void SongPlaylistSkipNext() {
			if(m_dqSongQueue.Count > 2) {
				m_dqSongQueue.AddLast(m_dqSongQueue.First);
				m_dqSongQueue.RemoveFirst();
			}

			PlaySongPlaylist();
		}

		/// <summary>
		/// 	This method will play the previous song in the play queue
		/// </summary>
		public void SongPlaylistSkipPrevious() {
			if(m_dqSongQueue.Count > 2) {
				m_dqSongQueue.AddFirst(m_dqSongQueue.Last);
				m_dqSongQueue.RemoveLast();
			}

			PlaySongPlaylist();
		}

		/// <summary>
		/// 	This method will clear the queue so that it can be repopulated with new songs
		/// </summary>
		public void ClearPlaylist() {
			MediaPlayer.Stop();
			m_dqSongQueue.Clear();
			Logger.Write(Logger.BuildString(GetType().Name).Append(": Song playlist cleared").ToString(), CurrentLoggingLevel);
		}

		#endregion

		#region Single Song methods

		/// <summary>
		/// 	Play a song, not from playlist
		/// </summary>
		/// <param name = "p_song">the name of the song</param>
		/// <param name = "p_loop">whether it loops or not</param>
		public void PlaySong(Song p_song, bool p_loop = false) {
			MediaPlayer.Play(p_song);
			MediaPlayer.IsRepeating = p_loop;
		}

		/// <summary>
		/// 	Stops the song (no resume available)
		/// </summary>
		public void StopSong() {
			MediaPlayer.Stop();
			Logger.Write(Logger.BuildString(GetType().Name).Append(": Song stopped").ToString(), CurrentLoggingLevel);
		}

		/// <summary>
		/// 	Pauses the song
		/// </summary>
		public void PauseSong() {
			MediaPlayer.Pause();
			Logger.Write(Logger.BuildString(GetType().Name).Append(": Song paused").ToString(), CurrentLoggingLevel);
		}

		/// <summary>
		/// 	Resumes the paused song
		/// </summary>
		public void ResumeSong() {
			MediaPlayer.Resume();
			Logger.Write(Logger.BuildString(GetType().Name).Append(": Song resumed").ToString(), CurrentLoggingLevel);
		}

		#endregion

		#region Soundeffect methods

		/// <summary>
		/// 	Plays a sound.  
		/// 	Note insure that the soundfile you are playing has the correct properties.
		/// 	i.e.  Content importer: mp3 audio
		/// 	Content processor: sound effect
		/// </summary>
		/// <param name = "p_soundName">name of the sound</param>
		public SoundEffectInstance PlaySound(SoundEffect p_soundName) {
			return PlaySound(p_soundName, 1.0f, 0.0f, 0.0f, false);
		}

		/// <summary>
		/// 	Plays a sound
		/// 	Note insure that the soundfile you are playing has the correct properties.
		/// 	i.e.  Content importer: mp3 audio
		/// 	Content processor: sound effect
		/// </summary>
		/// <param name = "p_soundName">name of the sound</param>
		/// <param name = "p_volume">volume (0.0f to 1.0f)</param>
		public SoundEffectInstance PlaySound(SoundEffect p_soundName, float p_volume) {
			return PlaySound(p_soundName, p_volume, 0.0f, 0.0f, false);
		}

		/// <summary>
		/// 	Plays a sound
		/// 	Note insure that the soundfile you are playing has the correct properties.
		/// 	i.e.  Content importer: mp3 audio
		/// 	Content processor: sound effect
		/// </summary>
		/// <param name = "p_soundName">name of the sound</param>
		/// <param name = "p_volume">volume (0.0f [mute] to 1.0f [maximum])</param>
		/// <param name = "p_pitch">pitch (-1.0f [-1 octave] to 1.0f [+1 octave])</param>
		/// <param name = "p_pan">pan (-1.0f [left] to 1.0f [right]. 0.0f is center</param>
		/// <param name = "p_loop">whether the sound loops</param>
		/// <returns name = "temp">returns the created sound effect so that the user can stop it at their leasure</returns>
		public SoundEffectInstance PlaySound(SoundEffect p_soundName, float p_volume, float p_pitch, float p_pan, bool p_loop) {
			var sound = p_soundName.CreateInstance();
			sound.Volume = p_volume * m_soundEffectVolume;
			sound.IsLooped = p_loop;
			sound.Pan = p_pan;
			sound.Pitch = p_pitch;
			sound.Play();
			m_hsSoundEffects.Add(sound);

			return sound;
		}

		/// <summary>
		/// 	Pauses all of the currently playing sound effects
		/// </summary>
		public void PauseAllSoundEffects() {
			foreach(var sfx in m_hsSoundEffects) {
				sfx.Pause();
			}
			Logger.Write(Logger.BuildString(GetType().Name).Append(": Sound effects all paused").ToString(), CurrentLoggingLevel);
		}

		/// <summary>
		/// 	Resumes all paused sound effects
		/// </summary>
		public void ResumeAllSoundEffects() {
			foreach(var sfx in m_hsSoundEffects) {
				sfx.Resume();
			}
			Logger.Write(Logger.BuildString(GetType().Name).Append(": Sound effects all resumed").ToString(), CurrentLoggingLevel);
		}

		/// <summary>
		/// 	Stops all sound effects
		/// </summary>
		public void StopAllSoundEffects() {
			foreach(var sfx in m_hsSoundEffects) {
				m_hsDoneSoundEffects.Add(sfx);
			}
			Logger.Write(Logger.BuildString(GetType().Name).Append(": Sound effects all stopped").ToString(), CurrentLoggingLevel);
		}

		#endregion
	}
}